# SEE modeldata package for new datasets
library(tidyverse)         # for graphing and data cleaning
library(tidymodels)        # for modeling
library(stacks)            # for stacking models
library(naniar)            # for examining missing values (NAs)
library(lubridate)         # for date manipulation
library(moderndive)        # for King County housing data
library(DALEX)             # for model interpretation  
library(DALEXtra)          # for extension of DALEX
library(patchwork)         # for combining plots nicely
library(dbplyr) 
library(scales)                # for SQL query "cheating" - part of tidyverse but needs to be loaded separately
library(mdsr)              # for accessing some databases - goes with Modern Data Science with R textbook
library(kableExtra)
library(RMySQL)            # for accessing MySQL databases
library(RSQLite)           # for accessing SQLite databases

#mapping
library(maps)              # for built-in maps
library(sf)                # for making maps using geom_sf
library(ggthemes)          # Lisa added - I like theme_map() for maps :)

#tidytext
library(tidytext)          # for text analysis, the tidy way!
library(textdata)          
library(reshape2)
library(wordcloud)         # for wordcloud
library(stopwords)

theme_set(theme_minimal()) # Lisa's favorite theme

When you finish the assignment, remove the # from the options chunk at the top, so that messages and warnings aren’t printed. If you are getting errors in your code, add error = TRUE so that the file knits. I would recommend not removing the # until you are completely finished.

Put it on GitHub!

From now on, GitHub should be part of your routine when doing assignments. I recommend making it part of your process anytime you are working in R, but I’ll make you show it’s part of your process for assignments.

Task: When you are finished with the assignment, post a link below to the GitHub repo for the assignment. If you want to post it to your personal website, that’s ok (not required). Make sure the link goes to a spot in the repo where I can easily find this assignment. For example, if you have a website with a blog and post the assignment as a blog post, link to the post’s folder in the repo. As an example, I’ve linked to my GitHub stacking material here.

https://github.com/jakebulling/HW3.git

Local Interpretable Machine Learning

You are going to use the King County house data and the same random forest model to predict log_price that I used in the tutorial.

data("house_prices")

# Create log_price and drop price variable
house_prices <- house_prices %>% 
  mutate(log_price = log(price, base = 10)) %>% 
  # make all integers numeric ... fixes prediction problem
  mutate(across(where(is.integer), as.numeric)) %>% 
  select(-price)


set.seed(327) #for reproducibility

# Randomly assigns 75% of the data to training.
house_split <- initial_split(house_prices, 
                             prop = .75)
house_training <- training(house_split)
house_testing <- testing(house_split)

ranger_recipe <- 
  recipe(formula = log_price ~ ., 
         data = house_training) %>% 
  step_date(date, 
            features = "month") %>% 
  # Make these evaluative variables, not included in modeling
  update_role(all_of(c("id",
                       "date")),
              new_role = "evaluative")

#define model
ranger_spec <- 
  rand_forest(mtry = 6, 
              min_n = 10, 
              trees = 200) %>% 
  set_mode("regression") %>% 
  set_engine("ranger")

#create workflow
ranger_workflow <- 
  workflow() %>% 
  add_recipe(ranger_recipe) %>% 
  add_model(ranger_spec) 

#fit the model
set.seed(712) # for reproducibility - random sampling in random forest choosing number of variables
ranger_fit <- ranger_workflow %>% 
  fit(house_training)

Tasks:

  1. Choose 3 new observations and do the following for each observation:
  • Construct a break-down plot using the default ordering. Interpret the resulting graph. Which variables contribute most to each observation’s prediction?
rf_explain <- 
  explain_tidymodels(
    model = ranger_fit,
    data = house_training %>% select(-log_price), 
    y = house_training %>%  pull(log_price),
    label = "rf"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  rf 
##   -> data              :  16210  rows  20  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  16210  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task regression (  default  ) 
##   -> predicted values  :  numerical, min =  5.081823 , mean =  5.66507 , max =  6.726297  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -0.3147814 , mean =  0.0004295329 , max =  0.2270978  
##   A new explainer has been created! 
obs4 <- house_training %>%
  slice(4)
obs250 <- house_training %>%
  slice(250)
obs1000 <- house_training %>%
  slice(1000)
plot(predict_parts(explainer = rf_explain,
              new_observation = obs4,
              type = "break_down"))

plot(predict_parts(explainer = rf_explain,
              new_observation = obs250,
              type = "break_down"))

plot(predict_parts(explainer = rf_explain,
              new_observation = obs1000,
              type = "break_down"))

The latitude variables are the ones that have the largest effect on the prediction and square foot living is near the largest effect as well.

  • Construct a SHAP graph and interpret it. Does it tell a similar story to the break-down plot?
rf_shap <-predict_parts(explainer = rf_explain,
                        new_observation = obs4,
                        type = "shap",
                        B = 10)

rf_shap_250 <-predict_parts(explainer = rf_explain,
                        new_observation = obs250,
                        type = "shap",
                        B = 10)
rf_shap_1000 <-predict_parts(explainer = rf_explain,
                        new_observation = obs1000,
                        type = "shap",
                        B = 10)

plot(rf_shap) 

plot(rf_shap_250) 

plot(rf_shap_1000)

These results are consistent with the results obtained earlier that latitude, longitude, and the square foot living are the largest contributors.

  • Construct a LIME graph (follow my code carefully). How close is each original prediction to the prediction from the local model? Interpret the result. You can also try using fewer or more variables in the local model than I used in the example.
set.seed(494)
model_type.dalex_explainer <- DALEXtra::model_type.dalex_explainer
predict_model.dalex_explainer <- DALEXtra::predict_model.dalex_explainer

lime_rf <- predict_surrogate(explainer = rf_explain,
                             new_observation = obs4 %>%
                               select(-log_price), 
                             n_features = 5,
                             n_permutations = 1000,
                             type = "lime")
plot(lime_rf) + 
  labs(x = "Variable")

  1. Describe how you would use the interpretable machine learning tools we’ve learned (both local and global) in future machine learning projects? How does each of them help you?

I would use the interpretable machine learning tools to help me discover which variables I have that are most important to determining our predictions. This could help us further study the relationships that we observe from the interpretable machine learning tools.

SQL

You will use the airlines data from the SQL database that I used in the example in the tutorial. Be sure to include the chunk to connect to the database here. And, when you are finished, disconnect. You may need to reconnect throughout as it times out after a while.

Tasks:

  1. Create a SQL chunk and an equivalent R code chunk that does the following: for each airport (with its name, not code), year, and month find the total number of departing flights, the average distance of the flight, and the proportion of flights that arrived more than 20 minutes late. In the R code chunk, write this out to a dataset. (HINT: 1. start small! 2. you may want to do the R part first and use it to “cheat” into the SQL code).
  • With the dataset you wrote out, create a graph that helps illustrate the “worst” airports in terms of late arrivals. You have some freedom in how you define worst and you may want to consider some of the other variables you computed. Do some theming to make your graph look glamorous (those of you who weren’t in my intro data science class this year may want to watch Will Chase’s Glamour of Graphics talk for inspiration).
con_air <- dbConnect_scidb("airlines")

flights_df <- tbl(con_air, "flights") 


airports_df <- tbl(con_air, "airports")



flight_airport_df <- 
  flights_df %>%
  filter(year == 2017) %>%
  mutate(faa = origin) %>%
  group_by(origin, month) %>%
  summarize(num_depart = n(),
             avg_distance = mean(distance),
             avg_delay = mean(arr_delay > 20)) %>%
  inner_join(airports_df, by = c("origin" = "faa"))


flight_airport_df %>%
  show_query()
## <SQL>
## SELECT `origin`, `month`, `num_depart`, `avg_distance`, `avg_delay`, `name`, `lat`, `lon`, `alt`, `tz`, `dst`, `city`, `country`
## FROM (SELECT `origin`, `month`, COUNT(*) AS `num_depart`, AVG(`distance`) AS `avg_distance`, AVG(`arr_delay` > 20.0) AS `avg_delay`
## FROM (SELECT `year`, `month`, `day`, `dep_time`, `sched_dep_time`, `dep_delay`, `arr_time`, `sched_arr_time`, `arr_delay`, `carrier`, `tailnum`, `flight`, `origin`, `dest`, `air_time`, `distance`, `cancelled`, `diverted`, `hour`, `minute`, `time_hour`, `origin` AS `faa`
## FROM `flights`
## WHERE (`year` = 2017.0)) `q01`
## GROUP BY `origin`, `month`) `LHS`
## INNER JOIN `airports` AS `RHS`
## ON (`LHS`.`origin` = `RHS`.`faa`)
flight_airport_rdf <- flight_airport_df %>%
  collect()
SELECT `origin`, `month`, `num_depart`, `avg_distance`, `avg_delay`, `name`, `lat`, `lon`, `alt`, `tz`, `dst`, `city`, `country`
FROM (SELECT `origin`, `month`, COUNT(*) AS `num_depart`, AVG(`distance`) AS `avg_distance`, AVG(`arr_delay` > 20.0) AS `avg_delay`
FROM (SELECT `year`, `month`, `day`, `dep_time`, `sched_dep_time`, `dep_delay`, `arr_time`, `sched_arr_time`, `arr_delay`, `carrier`, `tailnum`, `flight`, `origin`, `dest`, `air_time`, `distance`, `cancelled`, `diverted`, `hour`, `minute`, `time_hour`, `origin` AS `faa`
FROM `flights`
WHERE (`year` = 2017.0)) `q01`
GROUP BY `origin`, `month`) `LHS`
INNER JOIN `airports` AS `RHS`
ON (`LHS`.`origin` = `RHS`.`faa`)
Displaying records 1 - 10
origin month num_depart avg_distance avg_delay name lat lon alt tz dst city country
ABE 3 167 560.6766 0.1617 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 4 139 586.3525 0.1367 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 5 160 574.4938 0.1125 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 6 134 572.2313 0.1119 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 7 192 573.5104 0.1354 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 8 180 562.2167 0.1000 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 9 206 606.0874 0.0777 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 10 253 593.4704 0.1344 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 11 220 585.3227 0.0909 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
ABE 1 191 579.8796 0.1466 Lehigh Valley Intl 40.65208 -75.44081 393 -5 A Allentown United States
flight_airport_rdf %>%
  filter(num_depart >1000) %>%
  mutate(name = fct_reorder(name, avg_delay)) %>%
  ggplot() + 
  aes(x = avg_delay, 
      y = fct_reorder(name, avg_delay)) + 
  geom_col(fill = "lightblue")+ 
  scale_x_continuous(expand = c(0,0), 
                     labels = percent) + 
  labs(x = NULL,
       y = NULL,
       title = "Which airlines had the largest % of flights that \nwere more than 20 minutes late from 2010-2017?") +
  theme_minimal()

  • Although your graph was truly inspirational, you’ve been requested to “boil it down to a few numbers.” Some people just don’t appreciate all that effort you put in. And, you need to use the already summarized data that you already pulled in from SQL. Create a table with 6 or fewer rows and 3 or fewer columns that summarizes which airport is the “worst” in terms of late arrivals. Be careful with your calculations. You may consider using the kable, kableExtra, or gt packages to make your table look truly spectacular.
table_df<-flight_airport_rdf %>%
  select(num_depart, avg_delay, name) %>%
  group_by(name)%>%
  summarise(prop_late_over_20 = mean(avg_delay), 
            num_depart = sum(num_depart)) %>%
  filter(num_depart > 1000) %>%
  arrange(desc(prop_late_over_20)) %>%
  head(6)

table_df %>%
  kbl(caption = "Worst Airports by Proportion Flights late over 20 minutes.(min 1000 flights)") %>%
  kable_classic(full_width = F, html_font = "Cambria")
Worst Airports by Proportion Flights late over 20 minutes.(min 1000 flights)
name prop_late_over_20 num_depart
Key Field 0.2485000 1052
Newark Liberty Intl 0.2142000 115968
Arcata 0.2122833 1202
Charles M Schulz Sonoma Co 0.2084091 1111
Burlington Intl 0.2076250 3274
San Francisco Intl 0.2063500 174631
  1. Come up with your own interesting question that data in the airlines database can help you answer. Write a SQL query and equivalent R code chunk to extract the data you need and create an elegant graph to help answer the question. Be sure to write down the question so it is clear.

Which airport was the the most popular destination?

most_delay_time <- 
  flights_df %>%
  filter(year == 2015) %>%
  group_by(dest) %>%
  summarize(flights_received = n()) %>%
  inner_join(airports_df, by = c("dest" = "faa")) %>%
  ungroup() %>%
  group_by(name)

most_delay_time_df <- most_delay_time %>%
  collect()

most_delay_time_df%>%
  arrange(desc(flights_received))
most_delay_time_df %>%
  filter(flights_received > 10000)%>%
  ggplot() + 
  aes(x = flights_received, y = fct_reorder(name, flights_received)) + 
  geom_col(fill = "navy blue") + 
  labs(x = NULL, 
       y = NULL, 
       title = "Which airport recieved the largest amount of flights in 2015? ") + 
  scale_x_continuous(labels = scales::comma) + 
  theme_minimal()

Function Friday

If you need to revisit the material, it is posted on the moodle page. I’ve tried to add all the necessary libraries to the top, but I may have missed something.

geom_sf() tasks:

Using the example from class that we presented as a baseline (or your own if you really want to be ambitious), try to add the following components to the map of the contiguous United States:

  1. Change the color scheme of the map from the default blue (one option could be viridis).
states <- st_as_sf(maps::map("state", plot = FALSE, fill = TRUE))
states <- states %>%
  mutate(area = as.numeric(st_area(states)))


ggplot(data = states) +
  geom_sf(aes(fill = area), color = "black") +
  coord_sf(xlim = c(-127, -63), ylim = c(24, 51), expand = FALSE) +
  scale_fill_gradient(low = "white", high = "purple") +
  theme_map()

  1. Add a dot (or any symbol you want) to the centroid of each state.
?stat_sf_coordinates
ggplot(data = states) +
  geom_sf(aes(fill = area) , color = "black") +
  stat_sf_coordinates() +
  coord_sf(xlim = c(-127, -63), ylim = c(24, 51), expand = FALSE) +
  scale_fill_gradient(low = "white", high = "purple") +
  theme_map()

  1. Add a layer onto the map with the counties.
counties <- st_as_sf(maps::map("county", plot = FALSE, fill = TRUE))
counties <- counties %>%
  mutate(area = as.numeric(st_area(counties)))



ggplot(data = states) +
    geom_sf(data = counties, aes(fill = area), color = "black") + 
  coord_sf() + 
  scale_fill_gradient(low = "light blue", high = "purple") + 
  theme_map()

  1. Change the coordinates of the map to zoom in on your favorite state.
ggplot(data = states) +
    geom_sf(aes(fill = area), color = "black")+ 
  coord_sf(xlim = c(-109, -103), ylim = c(30, 37)) + 
  scale_fill_gradient(low = "light blue", high = "purple")  

Hint: https://www.r-spatial.org/r/2018/10/25/ggplot2-sf-2.html is a useful reference for some of the questions

tidytext tasks:

Now you will try using tidytext on a new dataset about Russian Troll tweets.

Read about the data

These are tweets from Twitter handles that are connected to the Internet Research Agency (IRA), a Russian “troll factory.” The majority of these tweets were posted from 2015-2017, but the datasets encompass tweets from February 2012 to May 2018.

Three of the main categories of troll tweet that we will be focusing on are Left Trolls, Right Trolls, and News Feed. Left Trolls usually pretend to be BLM activists, aiming to divide the democratic party (in this context, being pro-Bernie so that votes are taken away from Hillary). Right trolls imitate Trump supporters, and News Feed handles are “local news aggregators,” typically linking to legitimate news.

For our upcoming analyses, some important variables are:

  • author (handle sending the tweet)
  • content (text of the tweet)
  • language (language of the tweet)
  • publish_date (date and time the tweet was sent)

Variable documentation can be found on Github and a more detailed description of the dataset can be found in this fivethirtyeight article.

Because there are 12 datasets containing 2,973,371 tweets sent by 2,848 Twitter handles in total, we will be using three of these datasets (one from a Right troll, one from a Left troll, and one from a News Feed account).



  1. Read in Troll Tweets Dataset - this takes a while. You can cache it so you don’t need to read it in again each time you knit. Be sure to remove the eval=FALSE!!!!
troll_tweets <- read_csv("https://raw.githubusercontent.com/fivethirtyeight/russian-troll-tweets/master/IRAhandle_tweets_12.csv")
  1. Basic Data Cleaning and Exploration
  1. Remove rows where the tweet was in a language other than English
  2. Report the dimensions of the dataset
  3. Create two or three basic exploratory plots of the data (ex. plot of the different locations from which tweets were posted, plot of the account category of a tweet)
#A
new_trolls <- troll_tweets %>%
  filter(language == "English")

#B

dim(new_trolls)
## [1] 175966     21
#C
ggplot(new_trolls) + 
  aes(x = account_category) + 
  geom_bar()

ggplot(new_trolls) + 
  aes(x = followers, y = following) + 
  geom_point()

  1. Unnest Tokens

We want each row to represent a word from a tweet, rather than an entire tweet. Be sure to remove the eval=FALSE!!!!

troll_tweets_untoken <- new_trolls %>%
  unnest_tokens(word, content)

troll_tweets_untoken



  1. Remove stopwords. Be sure to remove the eval=FALSE!!!!
#get rid of stopwords (the, and, etc.)
troll_tweets_cleaned <- troll_tweets_untoken %>%
  anti_join(stop_words)

Take a look at the troll_tweets_cleaned dataset. Are there any other words/letters/numbers that we want to eliminate that weren’t taken care of by stop_words? Be sure to remove the eval=FALSE!!!!

#get rid of http, https, t.co, rt, amp, single number digits, and singular letters
troll_tweets_cleaned <- troll_tweets_cleaned %>%
  filter(word != "http", 
         word != "https",
         word !=  "t.co",
         word != "rt",
         word != "amp", 
         word != "1", 
         word != "2", 
         word != "3", 
         word != "4", 
         word != "5", 
         word != "6", 
         word != "7", 
         word != "8", 
         word != "9") # you can use %in% for a list of words
  1. Look at a subset of the tweets to see how often the top words appear.
troll_tweets_small <- troll_tweets_cleaned %>%
  count(word) %>%
  slice_max(order_by = n, n = 50) # 50 most occurring words

# visualize the number of times the 50 top words appear
ggplot(troll_tweets_small, 
       aes(y = fct_reorder(word,n), x = n)) +
  geom_col()

  1. Sentiment Analysis
  1. Get the sentiments using the “bing” parameter (which classifies words into “positive” or “negative”)
  2. Report how many positive and negative words there are in the dataset. Are there more positive or negative words, and why do you think this might be?

Be sure to remove the eval=FALSE!!!!

# look at sentiment
sentiments <-get_sentiments("bing")

# assign a sentiment to each word that has one associated
troll_tweets_sentiment <- troll_tweets_cleaned %>%
  inner_join(sentiments)

# count the sentiments
troll_tweets_sentiment %>%
  count(sentiment)

There are more negative words than positive because the fake tweets are trying to tear down the competition with their tweets.

  1. Using the troll_tweets_small dataset, make a wordcloud:
  1. That is sized by the number of times that a word appears in the tweets
  2. That is colored by sentiment (positive or negative)

Be sure to remove the eval=FALSE!!!!

# make a wordcloud where the size of the word is based on the number of times the word appears across the tweets

troll_tweets_small %>%
  with(wordcloud(word, n, max.words = 12))

# make a wordcloud colored by sentiment

troll_tweets_sentiment %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("blue", "orange"),
                   max.words = 20)

Are there any words whose categorization as “positive” or “negative” surprised you?

trump as positive surprised me for sure as breaking being negative just because of the many different uses of the word breaking, like breaking news, isn’t necessarily negative.

Projects

Read the project description on the moodle page. Talk to your group members about potential topics.

Task:

Write a short paragraph about ideas you have. If you already have some data sources in mind, you can link to those, but I’m more concerned with you having a topic that you’re interested in investigating right now.

We are considering doing projects related to sports or crime, we have thought of using fifa video game data to predict biases of the game, we have also come up with the idea to cluster different baseball players into different player types, and then analyzing what team makeup makes the best team. We also have discussed creating a recommender project, similar to netflix or google searches.

“Undoing” bias

Task:

Read this tweet thread by Deb Raji who you may remember from the Coded Bias film. Write a short paragraph that discusses at least one of the misconceptions.

It was interesting to me when she brought up the point that the bias can begin anywhere in the process. All the way back to the data collection process things can be biased. When you are doing your preprocessing, the process could be biased. I think that this needs to be recognized even more and thought about while working with data.

LS0tDQp0aXRsZTogJ0Fzc2lnbm1lbnQgIzMnDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkNCmBgYA0KDQpgYGB7ciBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0V9DQojIFNFRSBtb2RlbGRhdGEgcGFja2FnZSBmb3IgbmV3IGRhdGFzZXRzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAjIGZvciBncmFwaGluZyBhbmQgZGF0YSBjbGVhbmluZw0KbGlicmFyeSh0aWR5bW9kZWxzKSAgICAgICAgIyBmb3IgbW9kZWxpbmcNCmxpYnJhcnkoc3RhY2tzKSAgICAgICAgICAgICMgZm9yIHN0YWNraW5nIG1vZGVscw0KbGlicmFyeShuYW5pYXIpICAgICAgICAgICAgIyBmb3IgZXhhbWluaW5nIG1pc3NpbmcgdmFsdWVzIChOQXMpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICAgICAgICAjIGZvciBkYXRlIG1hbmlwdWxhdGlvbg0KbGlicmFyeShtb2Rlcm5kaXZlKSAgICAgICAgIyBmb3IgS2luZyBDb3VudHkgaG91c2luZyBkYXRhDQpsaWJyYXJ5KERBTEVYKSAgICAgICAgICAgICAjIGZvciBtb2RlbCBpbnRlcnByZXRhdGlvbiAgDQpsaWJyYXJ5KERBTEVYdHJhKSAgICAgICAgICAjIGZvciBleHRlbnNpb24gb2YgREFMRVgNCmxpYnJhcnkocGF0Y2h3b3JrKSAgICAgICAgICMgZm9yIGNvbWJpbmluZyBwbG90cyBuaWNlbHkNCmxpYnJhcnkoZGJwbHlyKSANCmxpYnJhcnkoc2NhbGVzKSAgICAgICAgICAgICAgICAjIGZvciBTUUwgcXVlcnkgImNoZWF0aW5nIiAtIHBhcnQgb2YgdGlkeXZlcnNlIGJ1dCBuZWVkcyB0byBiZSBsb2FkZWQgc2VwYXJhdGVseQ0KbGlicmFyeShtZHNyKSAgICAgICAgICAgICAgIyBmb3IgYWNjZXNzaW5nIHNvbWUgZGF0YWJhc2VzIC0gZ29lcyB3aXRoIE1vZGVybiBEYXRhIFNjaWVuY2Ugd2l0aCBSIHRleHRib29rDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KFJNeVNRTCkgICAgICAgICAgICAjIGZvciBhY2Nlc3NpbmcgTXlTUUwgZGF0YWJhc2VzDQpsaWJyYXJ5KFJTUUxpdGUpICAgICAgICAgICAjIGZvciBhY2Nlc3NpbmcgU1FMaXRlIGRhdGFiYXNlcw0KDQojbWFwcGluZw0KbGlicmFyeShtYXBzKSAgICAgICAgICAgICAgIyBmb3IgYnVpbHQtaW4gbWFwcw0KbGlicmFyeShzZikgICAgICAgICAgICAgICAgIyBmb3IgbWFraW5nIG1hcHMgdXNpbmcgZ2VvbV9zZg0KbGlicmFyeShnZ3RoZW1lcykgICAgICAgICAgIyBMaXNhIGFkZGVkIC0gSSBsaWtlIHRoZW1lX21hcCgpIGZvciBtYXBzIDopDQoNCiN0aWR5dGV4dA0KbGlicmFyeSh0aWR5dGV4dCkgICAgICAgICAgIyBmb3IgdGV4dCBhbmFseXNpcywgdGhlIHRpZHkgd2F5IQ0KbGlicmFyeSh0ZXh0ZGF0YSkgICAgICAgICAgDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeSh3b3JkY2xvdWQpICAgICAgICAgIyBmb3Igd29yZGNsb3VkDQpsaWJyYXJ5KHN0b3B3b3JkcykNCg0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkgIyBMaXNhJ3MgZmF2b3JpdGUgdGhlbWUNCmBgYA0KDQpXaGVuIHlvdSBmaW5pc2ggdGhlIGFzc2lnbm1lbnQsIHJlbW92ZSB0aGUgYCNgIGZyb20gdGhlIG9wdGlvbnMgY2h1bmsgYXQgdGhlIHRvcCwgc28gdGhhdCBtZXNzYWdlcyBhbmQgd2FybmluZ3MgYXJlbid0IHByaW50ZWQuIElmIHlvdSBhcmUgZ2V0dGluZyBlcnJvcnMgaW4geW91ciBjb2RlLCBhZGQgYGVycm9yID0gVFJVRWAgc28gdGhhdCB0aGUgZmlsZSBrbml0cy4gSSB3b3VsZCByZWNvbW1lbmQgbm90IHJlbW92aW5nIHRoZSBgI2AgdW50aWwgeW91IGFyZSBjb21wbGV0ZWx5IGZpbmlzaGVkLg0KDQojIyBQdXQgaXQgb24gR2l0SHViISAgICAgICAgDQoNCkZyb20gbm93IG9uLCBHaXRIdWIgc2hvdWxkIGJlIHBhcnQgb2YgeW91ciByb3V0aW5lIHdoZW4gZG9pbmcgYXNzaWdubWVudHMuIEkgcmVjb21tZW5kIG1ha2luZyBpdCBwYXJ0IG9mIHlvdXIgcHJvY2VzcyBhbnl0aW1lIHlvdSBhcmUgd29ya2luZyBpbiBSLCBidXQgSSdsbCBtYWtlIHlvdSBzaG93IGl0J3MgcGFydCBvZiB5b3VyIHByb2Nlc3MgZm9yIGFzc2lnbm1lbnRzLg0KDQoqKlRhc2sqKjogV2hlbiB5b3UgYXJlIGZpbmlzaGVkIHdpdGggdGhlIGFzc2lnbm1lbnQsIHBvc3QgYSBsaW5rIGJlbG93IHRvIHRoZSBHaXRIdWIgcmVwbyBmb3IgdGhlIGFzc2lnbm1lbnQuIElmIHlvdSB3YW50IHRvIHBvc3QgaXQgdG8geW91ciBwZXJzb25hbCB3ZWJzaXRlLCB0aGF0J3Mgb2sgKG5vdCByZXF1aXJlZCkuIE1ha2Ugc3VyZSB0aGUgbGluayBnb2VzIHRvIGEgc3BvdCBpbiB0aGUgcmVwbyB3aGVyZSBJIGNhbiBlYXNpbHkgZmluZCB0aGlzIGFzc2lnbm1lbnQuIEZvciBleGFtcGxlLCBpZiB5b3UgaGF2ZSBhIHdlYnNpdGUgd2l0aCBhIGJsb2cgYW5kIHBvc3QgdGhlIGFzc2lnbm1lbnQgYXMgYSBibG9nIHBvc3QsIGxpbmsgdG8gdGhlIHBvc3QncyBmb2xkZXIgaW4gdGhlIHJlcG8uIEFzIGFuIGV4YW1wbGUsIEkndmUgbGlua2VkIHRvIG15IEdpdEh1YiBzdGFja2luZyBtYXRlcmlhbCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2xsZW5kd2F5L2Fkc193ZWJzaXRlL3RyZWUvbWFzdGVyL19wb3N0cy8yMDIxLTAzLTIyLXN0YWNraW5nKS4NCg0KaHR0cHM6Ly9naXRodWIuY29tL2pha2VidWxsaW5nL0hXMy5naXQNCg0KDQojIyBMb2NhbCBJbnRlcnByZXRhYmxlIE1hY2hpbmUgTGVhcm5pbmcNCg0KWW91IGFyZSBnb2luZyB0byB1c2UgdGhlIEtpbmcgQ291bnR5IGhvdXNlIGRhdGEgYW5kIHRoZSBzYW1lIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdG8gcHJlZGljdCBgbG9nX3ByaWNlYCB0aGF0IEkgdXNlZCBpbiB0aGUgW3R1dG9yaWFsXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0zMS1pbWxsb2NhbC8pLg0KDQpgYGB7cn0NCmRhdGEoImhvdXNlX3ByaWNlcyIpDQoNCiMgQ3JlYXRlIGxvZ19wcmljZSBhbmQgZHJvcCBwcmljZSB2YXJpYWJsZQ0KaG91c2VfcHJpY2VzIDwtIGhvdXNlX3ByaWNlcyAlPiUgDQogIG11dGF0ZShsb2dfcHJpY2UgPSBsb2cocHJpY2UsIGJhc2UgPSAxMCkpICU+JSANCiAgIyBtYWtlIGFsbCBpbnRlZ2VycyBudW1lcmljIC4uLiBmaXhlcyBwcmVkaWN0aW9uIHByb2JsZW0NCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5pbnRlZ2VyKSwgYXMubnVtZXJpYykpICU+JSANCiAgc2VsZWN0KC1wcmljZSkNCg0KDQpzZXQuc2VlZCgzMjcpICNmb3IgcmVwcm9kdWNpYmlsaXR5DQoNCiMgUmFuZG9tbHkgYXNzaWducyA3NSUgb2YgdGhlIGRhdGEgdG8gdHJhaW5pbmcuDQpob3VzZV9zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGhvdXNlX3ByaWNlcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb3AgPSAuNzUpDQpob3VzZV90cmFpbmluZyA8LSB0cmFpbmluZyhob3VzZV9zcGxpdCkNCmhvdXNlX3Rlc3RpbmcgPC0gdGVzdGluZyhob3VzZV9zcGxpdCkNCg0KcmFuZ2VyX3JlY2lwZSA8LSANCiAgcmVjaXBlKGZvcm11bGEgPSBsb2dfcHJpY2UgfiAuLCANCiAgICAgICAgIGRhdGEgPSBob3VzZV90cmFpbmluZykgJT4lIA0KICBzdGVwX2RhdGUoZGF0ZSwgDQogICAgICAgICAgICBmZWF0dXJlcyA9ICJtb250aCIpICU+JSANCiAgIyBNYWtlIHRoZXNlIGV2YWx1YXRpdmUgdmFyaWFibGVzLCBub3QgaW5jbHVkZWQgaW4gbW9kZWxpbmcNCiAgdXBkYXRlX3JvbGUoYWxsX29mKGMoImlkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgImRhdGUiKSksDQogICAgICAgICAgICAgIG5ld19yb2xlID0gImV2YWx1YXRpdmUiKQ0KDQojZGVmaW5lIG1vZGVsDQpyYW5nZXJfc3BlYyA8LSANCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDYsIA0KICAgICAgICAgICAgICBtaW5fbiA9IDEwLCANCiAgICAgICAgICAgICAgdHJlZXMgPSAyMDApICU+JSANCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgDQogIHNldF9lbmdpbmUoInJhbmdlciIpDQoNCiNjcmVhdGUgd29ya2Zsb3cNCnJhbmdlcl93b3JrZmxvdyA8LSANCiAgd29ya2Zsb3coKSAlPiUgDQogIGFkZF9yZWNpcGUocmFuZ2VyX3JlY2lwZSkgJT4lIA0KICBhZGRfbW9kZWwocmFuZ2VyX3NwZWMpIA0KDQojZml0IHRoZSBtb2RlbA0Kc2V0LnNlZWQoNzEyKSAjIGZvciByZXByb2R1Y2liaWxpdHkgLSByYW5kb20gc2FtcGxpbmcgaW4gcmFuZG9tIGZvcmVzdCBjaG9vc2luZyBudW1iZXIgb2YgdmFyaWFibGVzDQpyYW5nZXJfZml0IDwtIHJhbmdlcl93b3JrZmxvdyAlPiUgDQogIGZpdChob3VzZV90cmFpbmluZykNCmBgYA0KDQoNCioqVGFza3M6KioNCg0KMS4gQ2hvb3NlIDMgbmV3IG9ic2VydmF0aW9ucyBhbmQgZG8gdGhlIGZvbGxvd2luZyBmb3IgZWFjaCBvYnNlcnZhdGlvbjogIA0KICAtIENvbnN0cnVjdCBhIGJyZWFrLWRvd24gcGxvdCB1c2luZyB0aGUgZGVmYXVsdCBvcmRlcmluZy4gSW50ZXJwcmV0IHRoZSByZXN1bHRpbmcgZ3JhcGguIFdoaWNoIHZhcmlhYmxlcyBjb250cmlidXRlIG1vc3QgdG8gZWFjaCBvYnNlcnZhdGlvbidzIHByZWRpY3Rpb24/ICANCiAgDQpgYGB7cn0NCnJmX2V4cGxhaW4gPC0gDQogIGV4cGxhaW5fdGlkeW1vZGVscygNCiAgICBtb2RlbCA9IHJhbmdlcl9maXQsDQogICAgZGF0YSA9IGhvdXNlX3RyYWluaW5nICU+JSBzZWxlY3QoLWxvZ19wcmljZSksIA0KICAgIHkgPSBob3VzZV90cmFpbmluZyAlPiUgIHB1bGwobG9nX3ByaWNlKSwNCiAgICBsYWJlbCA9ICJyZiINCiAgKQ0KDQpvYnM0IDwtIGhvdXNlX3RyYWluaW5nICU+JQ0KICBzbGljZSg0KQ0Kb2JzMjUwIDwtIGhvdXNlX3RyYWluaW5nICU+JQ0KICBzbGljZSgyNTApDQpvYnMxMDAwIDwtIGhvdXNlX3RyYWluaW5nICU+JQ0KICBzbGljZSgxMDAwKQ0KcGxvdChwcmVkaWN0X3BhcnRzKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG9iczQsDQogICAgICAgICAgICAgIHR5cGUgPSAiYnJlYWtfZG93biIpKQ0KDQpwbG90KHByZWRpY3RfcGFydHMoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwNCiAgICAgICAgICAgICAgbmV3X29ic2VydmF0aW9uID0gb2JzMjUwLA0KICAgICAgICAgICAgICB0eXBlID0gImJyZWFrX2Rvd24iKSkNCg0KcGxvdChwcmVkaWN0X3BhcnRzKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG9iczEwMDAsDQogICAgICAgICAgICAgIHR5cGUgPSAiYnJlYWtfZG93biIpKQ0KYGBgDQogIA0KICBUaGUgbGF0aXR1ZGUgdmFyaWFibGVzIGFyZSB0aGUgb25lcyB0aGF0IGhhdmUgdGhlIGxhcmdlc3QgZWZmZWN0IG9uIHRoZSBwcmVkaWN0aW9uIGFuZCBzcXVhcmUgZm9vdCBsaXZpbmcgaXMgbmVhciB0aGUgbGFyZ2VzdCBlZmZlY3QgYXMgd2VsbC4gDQogIA0KICAtIENvbnN0cnVjdCBhIFNIQVAgZ3JhcGggYW5kIGludGVycHJldCBpdC4gRG9lcyBpdCB0ZWxsIGEgc2ltaWxhciBzdG9yeSB0byB0aGUgYnJlYWstZG93biBwbG90PyAgDQoNCmBgYHtyfQ0KcmZfc2hhcCA8LXByZWRpY3RfcGFydHMoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG9iczQsDQogICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInNoYXAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwKQ0KDQpyZl9zaGFwXzI1MCA8LXByZWRpY3RfcGFydHMoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG9iczI1MCwNCiAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAic2hhcCIsDQogICAgICAgICAgICAgICAgICAgICAgICBCID0gMTApDQpyZl9zaGFwXzEwMDAgPC1wcmVkaWN0X3BhcnRzKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBvYnMxMDAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIEIgPSAxMCkNCg0KcGxvdChyZl9zaGFwKSANCnBsb3QocmZfc2hhcF8yNTApIA0KcGxvdChyZl9zaGFwXzEwMDApDQpgYGANClRoZXNlIHJlc3VsdHMgYXJlIGNvbnNpc3RlbnQgd2l0aCB0aGUgcmVzdWx0cyBvYnRhaW5lZCBlYXJsaWVyIHRoYXQgbGF0aXR1ZGUsIGxvbmdpdHVkZSwgYW5kIHRoZSBzcXVhcmUgZm9vdCBsaXZpbmcgYXJlIHRoZSBsYXJnZXN0IGNvbnRyaWJ1dG9ycy4gDQoNCg0KICAtIENvbnN0cnVjdCBhIExJTUUgZ3JhcGggKGZvbGxvdyBteSBjb2RlIGNhcmVmdWxseSkuIEhvdyBjbG9zZSBpcyBlYWNoIG9yaWdpbmFsIHByZWRpY3Rpb24gdG8gdGhlIHByZWRpY3Rpb24gZnJvbSB0aGUgbG9jYWwgbW9kZWw/IEludGVycHJldCB0aGUgcmVzdWx0LiBZb3UgY2FuIGFsc28gdHJ5IHVzaW5nIGZld2VyIG9yIG1vcmUgdmFyaWFibGVzIGluIHRoZSBsb2NhbCBtb2RlbCB0aGFuIEkgdXNlZCBpbiB0aGUgZXhhbXBsZS4gIA0KICANCmBgYHtyfQ0Kc2V0LnNlZWQoNDk0KQ0KbW9kZWxfdHlwZS5kYWxleF9leHBsYWluZXIgPC0gREFMRVh0cmE6Om1vZGVsX3R5cGUuZGFsZXhfZXhwbGFpbmVyDQpwcmVkaWN0X21vZGVsLmRhbGV4X2V4cGxhaW5lciA8LSBEQUxFWHRyYTo6cHJlZGljdF9tb2RlbC5kYWxleF9leHBsYWluZXINCg0KbGltZV9yZiA8LSBwcmVkaWN0X3N1cnJvZ2F0ZShleHBsYWluZXIgPSByZl9leHBsYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBvYnM0ICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtbG9nX3ByaWNlKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3Blcm11dGF0aW9ucyA9IDEwMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAibGltZSIpDQpwbG90KGxpbWVfcmYpICsgDQogIGxhYnMoeCA9ICJWYXJpYWJsZSIpDQoNCmBgYA0KICANCiAgDQogIA0KMi4gRGVzY3JpYmUgaG93IHlvdSB3b3VsZCB1c2UgdGhlIGludGVycHJldGFibGUgbWFjaGluZSBsZWFybmluZyB0b29scyB3ZSd2ZSBsZWFybmVkIChib3RoIGxvY2FsIGFuZCBnbG9iYWwpIGluIGZ1dHVyZSBtYWNoaW5lIGxlYXJuaW5nIHByb2plY3RzPyBIb3cgZG9lcyBlYWNoIG9mIHRoZW0gaGVscCB5b3U/DQoNCkkgd291bGQgdXNlIHRoZSBpbnRlcnByZXRhYmxlIG1hY2hpbmUgbGVhcm5pbmcgdG9vbHMgdG8gaGVscCBtZSBkaXNjb3ZlciB3aGljaCB2YXJpYWJsZXMgSSBoYXZlIHRoYXQgYXJlIG1vc3QgaW1wb3J0YW50IHRvIGRldGVybWluaW5nIG91ciBwcmVkaWN0aW9ucy4gVGhpcyBjb3VsZCBoZWxwIHVzIGZ1cnRoZXIgc3R1ZHkgdGhlIHJlbGF0aW9uc2hpcHMgdGhhdCB3ZSBvYnNlcnZlIGZyb20gdGhlIGludGVycHJldGFibGUgbWFjaGluZSBsZWFybmluZyB0b29scy4gDQoNCiMjIFNRTA0KDQpZb3Ugd2lsbCB1c2UgdGhlIGBhaXJsaW5lc2AgZGF0YSBmcm9tIHRoZSBTUUwgZGF0YWJhc2UgdGhhdCBJIHVzZWQgaW4gdGhlIGV4YW1wbGUgaW4gdGhlIFt0dXRvcmlhbF0oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Bvc3RzLzIwMjEtMDMtMjktc3FsaW5yLykuIEJlIHN1cmUgdG8gaW5jbHVkZSB0aGUgY2h1bmsgdG8gY29ubmVjdCB0byB0aGUgZGF0YWJhc2UgaGVyZS4gQW5kLCB3aGVuIHlvdSBhcmUgZmluaXNoZWQsIGRpc2Nvbm5lY3QuIFlvdSBtYXkgbmVlZCB0byByZWNvbm5lY3QgdGhyb3VnaG91dCBhcyBpdCB0aW1lcyBvdXQgYWZ0ZXIgYSB3aGlsZS4NCg0KKipUYXNrcyoqOg0KDQoxLiBDcmVhdGUgYSBTUUwgY2h1bmsgYW5kIGFuIGVxdWl2YWxlbnQgUiBjb2RlIGNodW5rIHRoYXQgZG9lcyB0aGUgZm9sbG93aW5nOiBmb3IgZWFjaCBhaXJwb3J0ICh3aXRoIGl0cyBuYW1lLCBub3QgY29kZSksIHllYXIsIGFuZCBtb250aCBmaW5kIHRoZSB0b3RhbCBudW1iZXIgb2YgZGVwYXJ0aW5nIGZsaWdodHMsIHRoZSBhdmVyYWdlIGRpc3RhbmNlIG9mIHRoZSBmbGlnaHQsIGFuZCB0aGUgcHJvcG9ydGlvbiBvZiBmbGlnaHRzIHRoYXQgYXJyaXZlZCBtb3JlIHRoYW4gMjAgbWludXRlcyBsYXRlLiBJbiB0aGUgUiBjb2RlIGNodW5rLCB3cml0ZSB0aGlzIG91dCB0byBhIGRhdGFzZXQuIChISU5UOiAxLiBzdGFydCBzbWFsbCEgMi4geW91IG1heSB3YW50IHRvIGRvIHRoZSBSIHBhcnQgZmlyc3QgYW5kIHVzZSBpdCB0byAiY2hlYXQiIGludG8gdGhlIFNRTCBjb2RlKS4NCg0KICAtIFdpdGggdGhlIGRhdGFzZXQgeW91IHdyb3RlIG91dCwgY3JlYXRlIGEgZ3JhcGggdGhhdCBoZWxwcyBpbGx1c3RyYXRlIHRoZSAid29yc3QiIGFpcnBvcnRzIGluIHRlcm1zIG9mIGxhdGUgYXJyaXZhbHMuIFlvdSBoYXZlIHNvbWUgZnJlZWRvbSBpbiBob3cgeW91IGRlZmluZSB3b3JzdCBhbmQgeW91IG1heSB3YW50IHRvIGNvbnNpZGVyIHNvbWUgb2YgdGhlIG90aGVyIHZhcmlhYmxlcyB5b3UgY29tcHV0ZWQuIERvIHNvbWUgdGhlbWluZyB0byBtYWtlIHlvdXIgZ3JhcGggbG9vayBnbGFtb3JvdXMgKHRob3NlIG9mIHlvdSB3aG8gd2VyZW4ndCBpbiBteSBpbnRybyBkYXRhIHNjaWVuY2UgY2xhc3MgdGhpcyB5ZWFyIG1heSB3YW50IHRvIHdhdGNoIFdpbGwgQ2hhc2UncyBbR2xhbW91ciBvZiBHcmFwaGljc10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1oNWNUYWNhV0U2SSkgdGFsayBmb3IgaW5zcGlyYXRpb24pLiANCiAgDQoNCiAgDQogIA0KYGBge3J9DQpjb25fYWlyIDwtIGRiQ29ubmVjdF9zY2lkYigiYWlybGluZXMiKQ0KDQpmbGlnaHRzX2RmIDwtIHRibChjb25fYWlyLCAiZmxpZ2h0cyIpIA0KDQoNCmFpcnBvcnRzX2RmIDwtIHRibChjb25fYWlyLCAiYWlycG9ydHMiKQ0KDQoNCg0KZmxpZ2h0X2FpcnBvcnRfZGYgPC0gDQogIGZsaWdodHNfZGYgJT4lDQogIGZpbHRlcih5ZWFyID09IDIwMTcpICU+JQ0KICBtdXRhdGUoZmFhID0gb3JpZ2luKSAlPiUNCiAgZ3JvdXBfYnkob3JpZ2luLCBtb250aCkgJT4lDQogIHN1bW1hcml6ZShudW1fZGVwYXJ0ID0gbigpLA0KICAgICAgICAgICAgIGF2Z19kaXN0YW5jZSA9IG1lYW4oZGlzdGFuY2UpLA0KICAgICAgICAgICAgIGF2Z19kZWxheSA9IG1lYW4oYXJyX2RlbGF5ID4gMjApKSAlPiUNCiAgaW5uZXJfam9pbihhaXJwb3J0c19kZiwgYnkgPSBjKCJvcmlnaW4iID0gImZhYSIpKQ0KDQoNCmZsaWdodF9haXJwb3J0X2RmICU+JQ0KICBzaG93X3F1ZXJ5KCkNCg0KZmxpZ2h0X2FpcnBvcnRfcmRmIDwtIGZsaWdodF9haXJwb3J0X2RmICU+JQ0KICBjb2xsZWN0KCkNCmBgYA0KDQogIA0KYGBge3NxbCBjb25uZWN0aW9uPWNvbl9haXJ9DQpTRUxFQ1QgYG9yaWdpbmAsIGBtb250aGAsIGBudW1fZGVwYXJ0YCwgYGF2Z19kaXN0YW5jZWAsIGBhdmdfZGVsYXlgLCBgbmFtZWAsIGBsYXRgLCBgbG9uYCwgYGFsdGAsIGB0emAsIGBkc3RgLCBgY2l0eWAsIGBjb3VudHJ5YA0KRlJPTSAoU0VMRUNUIGBvcmlnaW5gLCBgbW9udGhgLCBDT1VOVCgqKSBBUyBgbnVtX2RlcGFydGAsIEFWRyhgZGlzdGFuY2VgKSBBUyBgYXZnX2Rpc3RhbmNlYCwgQVZHKGBhcnJfZGVsYXlgID4gMjAuMCkgQVMgYGF2Z19kZWxheWANCkZST00gKFNFTEVDVCBgeWVhcmAsIGBtb250aGAsIGBkYXlgLCBgZGVwX3RpbWVgLCBgc2NoZWRfZGVwX3RpbWVgLCBgZGVwX2RlbGF5YCwgYGFycl90aW1lYCwgYHNjaGVkX2Fycl90aW1lYCwgYGFycl9kZWxheWAsIGBjYXJyaWVyYCwgYHRhaWxudW1gLCBgZmxpZ2h0YCwgYG9yaWdpbmAsIGBkZXN0YCwgYGFpcl90aW1lYCwgYGRpc3RhbmNlYCwgYGNhbmNlbGxlZGAsIGBkaXZlcnRlZGAsIGBob3VyYCwgYG1pbnV0ZWAsIGB0aW1lX2hvdXJgLCBgb3JpZ2luYCBBUyBgZmFhYA0KRlJPTSBgZmxpZ2h0c2ANCldIRVJFIChgeWVhcmAgPSAyMDE3LjApKSBgcTAxYA0KR1JPVVAgQlkgYG9yaWdpbmAsIGBtb250aGApIGBMSFNgDQpJTk5FUiBKT0lOIGBhaXJwb3J0c2AgQVMgYFJIU2ANCk9OIChgTEhTYC5gb3JpZ2luYCA9IGBSSFNgLmBmYWFgKQ0KYGBgDQoNCiAgDQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTE1fQ0KDQoNCmZsaWdodF9haXJwb3J0X3JkZiAlPiUNCiAgZmlsdGVyKG51bV9kZXBhcnQgPjEwMDApICU+JQ0KICBtdXRhdGUobmFtZSA9IGZjdF9yZW9yZGVyKG5hbWUsIGF2Z19kZWxheSkpICU+JQ0KICBnZ3Bsb3QoKSArIA0KICBhZXMoeCA9IGF2Z19kZWxheSwgDQogICAgICB5ID0gZmN0X3Jlb3JkZXIobmFtZSwgYXZnX2RlbGF5KSkgKyANCiAgZ2VvbV9jb2woZmlsbCA9ICJsaWdodGJsdWUiKSsgDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gcGVyY2VudCkgKyANCiAgbGFicyh4ID0gTlVMTCwNCiAgICAgICB5ID0gTlVMTCwNCiAgICAgICB0aXRsZSA9ICJXaGljaCBhaXJsaW5lcyBoYWQgdGhlIGxhcmdlc3QgJSBvZiBmbGlnaHRzIHRoYXQgXG53ZXJlIG1vcmUgdGhhbiAyMCBtaW51dGVzIGxhdGUgZnJvbSAyMDEwLTIwMTc/IikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCg0KICAtIEFsdGhvdWdoIHlvdXIgZ3JhcGggd2FzIHRydWx5IGluc3BpcmF0aW9uYWwsIHlvdSd2ZSBiZWVuIHJlcXVlc3RlZCB0byAiYm9pbCBpdCBkb3duIHRvIGEgZmV3IG51bWJlcnMuIiBTb21lIHBlb3BsZSBqdXN0IGRvbid0IGFwcHJlY2lhdGUgYWxsIHRoYXQgZWZmb3J0IHlvdSBwdXQgaW4uIEFuZCwgeW91IG5lZWQgdG8gdXNlIHRoZSBhbHJlYWR5IHN1bW1hcml6ZWQgZGF0YSB0aGF0IHlvdSBhbHJlYWR5IHB1bGxlZCBpbiBmcm9tIFNRTC4gQ3JlYXRlIGEgdGFibGUgd2l0aCA2IG9yIGZld2VyIHJvd3MgYW5kIDMgb3IgZmV3ZXIgY29sdW1ucyB0aGF0IHN1bW1hcml6ZXMgd2hpY2ggYWlycG9ydCBpcyB0aGUgIndvcnN0IiBpbiB0ZXJtcyBvZiBsYXRlIGFycml2YWxzLiBCZSBjYXJlZnVsIHdpdGggeW91ciBjYWxjdWxhdGlvbnMuIFlvdSBtYXkgY29uc2lkZXIgdXNpbmcgdGhlIGBrYWJsZWAsIGBrYWJsZUV4dHJhYCwgb3IgYGd0YCBwYWNrYWdlcyB0byBtYWtlIHlvdXIgdGFibGUgbG9vayB0cnVseSBzcGVjdGFjdWxhci4NCiAgDQogIA0KICANCmBgYHtyfQ0KdGFibGVfZGY8LWZsaWdodF9haXJwb3J0X3JkZiAlPiUNCiAgc2VsZWN0KG51bV9kZXBhcnQsIGF2Z19kZWxheSwgbmFtZSkgJT4lDQogIGdyb3VwX2J5KG5hbWUpJT4lDQogIHN1bW1hcmlzZShwcm9wX2xhdGVfb3Zlcl8yMCA9IG1lYW4oYXZnX2RlbGF5KSwgDQogICAgICAgICAgICBudW1fZGVwYXJ0ID0gc3VtKG51bV9kZXBhcnQpKSAlPiUNCiAgZmlsdGVyKG51bV9kZXBhcnQgPiAxMDAwKSAlPiUNCiAgYXJyYW5nZShkZXNjKHByb3BfbGF0ZV9vdmVyXzIwKSkgJT4lDQogIGhlYWQoNikNCg0KdGFibGVfZGYgJT4lDQogIGtibChjYXB0aW9uID0gIldvcnN0IEFpcnBvcnRzIGJ5IFByb3BvcnRpb24gRmxpZ2h0cyBsYXRlIG92ZXIgMjAgbWludXRlcy4obWluIDEwMDAgZmxpZ2h0cykiKSAlPiUNCiAga2FibGVfY2xhc3NpYyhmdWxsX3dpZHRoID0gRiwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KYGBgDQoNCiAgDQogIA0KMi4gQ29tZSB1cCB3aXRoIHlvdXIgb3duIGludGVyZXN0aW5nIHF1ZXN0aW9uIHRoYXQgZGF0YSBpbiB0aGUgYWlybGluZXMgZGF0YWJhc2UgY2FuIGhlbHAgeW91IGFuc3dlci4gV3JpdGUgYSBTUUwgcXVlcnkgYW5kIGVxdWl2YWxlbnQgUiBjb2RlIGNodW5rIHRvIGV4dHJhY3QgdGhlIGRhdGEgeW91IG5lZWQgYW5kIGNyZWF0ZSBhbiBlbGVnYW50IGdyYXBoIHRvIGhlbHAgYW5zd2VyIHRoZSBxdWVzdGlvbi4gQmUgc3VyZSB0byB3cml0ZSBkb3duIHRoZSBxdWVzdGlvbiBzbyBpdCBpcyBjbGVhci4gDQoNCldoaWNoIGFpcnBvcnQgd2FzIHRoZSB0aGUgbW9zdCBwb3B1bGFyIGRlc3RpbmF0aW9uPyAgDQoNCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0xMH0NCm1vc3RfZGVsYXlfdGltZSA8LSANCiAgZmxpZ2h0c19kZiAlPiUNCiAgZmlsdGVyKHllYXIgPT0gMjAxNSkgJT4lDQogIGdyb3VwX2J5KGRlc3QpICU+JQ0KICBzdW1tYXJpemUoZmxpZ2h0c19yZWNlaXZlZCA9IG4oKSkgJT4lDQogIGlubmVyX2pvaW4oYWlycG9ydHNfZGYsIGJ5ID0gYygiZGVzdCIgPSAiZmFhIikpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGdyb3VwX2J5KG5hbWUpDQoNCm1vc3RfZGVsYXlfdGltZV9kZiA8LSBtb3N0X2RlbGF5X3RpbWUgJT4lDQogIGNvbGxlY3QoKQ0KDQptb3N0X2RlbGF5X3RpbWVfZGYlPiUNCiAgYXJyYW5nZShkZXNjKGZsaWdodHNfcmVjZWl2ZWQpKQ0KDQoNCm1vc3RfZGVsYXlfdGltZV9kZiAlPiUNCiAgZmlsdGVyKGZsaWdodHNfcmVjZWl2ZWQgPiAxMDAwMCklPiUNCiAgZ2dwbG90KCkgKyANCiAgYWVzKHggPSBmbGlnaHRzX3JlY2VpdmVkLCB5ID0gZmN0X3Jlb3JkZXIobmFtZSwgZmxpZ2h0c19yZWNlaXZlZCkpICsgDQogIGdlb21fY29sKGZpbGwgPSAibmF2eSBibHVlIikgKyANCiAgbGFicyh4ID0gTlVMTCwgDQogICAgICAgeSA9IE5VTEwsIA0KICAgICAgIHRpdGxlID0gIldoaWNoIGFpcnBvcnQgcmVjaWV2ZWQgdGhlIGxhcmdlc3QgYW1vdW50IG9mIGZsaWdodHMgaW4gMjAxNT8gIikgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsgDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KDQoNCiMjIEZ1bmN0aW9uIEZyaWRheQ0KDQpJZiB5b3UgbmVlZCB0byByZXZpc2l0IHRoZSBtYXRlcmlhbCwgaXQgaXMgcG9zdGVkIG9uIHRoZSBtb29kbGUgcGFnZS4gSSd2ZSB0cmllZCB0byBhZGQgYWxsIHRoZSBuZWNlc3NhcnkgbGlicmFyaWVzIHRvIHRoZSB0b3AsIGJ1dCBJIG1heSBoYXZlIG1pc3NlZCBzb21ldGhpbmcuDQoNCioqYGdlb21fc2YoKWAgdGFza3MqKjoNCg0KVXNpbmcgdGhlIGV4YW1wbGUgZnJvbSBjbGFzcyB0aGF0IHdlIHByZXNlbnRlZCBhcyBhIGJhc2VsaW5lIChvciB5b3VyIG93biBpZiB5b3UgcmVhbGx5IHdhbnQgdG8gYmUgYW1iaXRpb3VzKSwgdHJ5IHRvIGFkZCB0aGUgZm9sbG93aW5nIGNvbXBvbmVudHMgdG8gdGhlIG1hcCBvZiB0aGUgY29udGlndW91cyBVbml0ZWQgU3RhdGVzOg0KDQoxLglDaGFuZ2UgdGhlIGNvbG9yIHNjaGVtZSBvZiB0aGUgbWFwIGZyb20gdGhlIGRlZmF1bHQgYmx1ZSAob25lIG9wdGlvbiBjb3VsZCBiZSB2aXJpZGlzKS4NCmBgYHtyfQ0Kc3RhdGVzIDwtIHN0X2FzX3NmKG1hcHM6Om1hcCgic3RhdGUiLCBwbG90ID0gRkFMU0UsIGZpbGwgPSBUUlVFKSkNCnN0YXRlcyA8LSBzdGF0ZXMgJT4lDQogIG11dGF0ZShhcmVhID0gYXMubnVtZXJpYyhzdF9hcmVhKHN0YXRlcykpKQ0KDQoNCmdncGxvdChkYXRhID0gc3RhdGVzKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSwgY29sb3IgPSAiYmxhY2siKSArDQogIGNvb3JkX3NmKHhsaW0gPSBjKC0xMjcsIC02MyksIHlsaW0gPSBjKDI0LCA1MSksIGV4cGFuZCA9IEZBTFNFKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIndoaXRlIiwgaGlnaCA9ICJwdXJwbGUiKSArDQogIHRoZW1lX21hcCgpDQpgYGANCg0KMi4JQWRkIGEgZG90IChvciBhbnkgc3ltYm9sIHlvdSB3YW50KSB0byB0aGUgY2VudHJvaWQgb2YgZWFjaCBzdGF0ZS4NCmBgYHtyfQ0KP3N0YXRfc2ZfY29vcmRpbmF0ZXMNCmdncGxvdChkYXRhID0gc3RhdGVzKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSAsIGNvbG9yID0gImJsYWNrIikgKw0KICBzdGF0X3NmX2Nvb3JkaW5hdGVzKCkgKw0KICBjb29yZF9zZih4bGltID0gYygtMTI3LCAtNjMpLCB5bGltID0gYygyNCwgNTEpLCBleHBhbmQgPSBGQUxTRSkgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIsIGhpZ2ggPSAicHVycGxlIikgKw0KICB0aGVtZV9tYXAoKQ0KDQpgYGANCg0KMy4JQWRkIGEgbGF5ZXIgb250byB0aGUgbWFwIHdpdGggdGhlIGNvdW50aWVzLg0KDQpgYGB7cn0NCmNvdW50aWVzIDwtIHN0X2FzX3NmKG1hcHM6Om1hcCgiY291bnR5IiwgcGxvdCA9IEZBTFNFLCBmaWxsID0gVFJVRSkpDQpjb3VudGllcyA8LSBjb3VudGllcyAlPiUNCiAgbXV0YXRlKGFyZWEgPSBhcy5udW1lcmljKHN0X2FyZWEoY291bnRpZXMpKSkNCg0KDQoNCmdncGxvdChkYXRhID0gc3RhdGVzKSArDQogICAgZ2VvbV9zZihkYXRhID0gY291bnRpZXMsIGFlcyhmaWxsID0gYXJlYSksIGNvbG9yID0gImJsYWNrIikgKyANCiAgY29vcmRfc2YoKSArIA0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJsaWdodCBibHVlIiwgaGlnaCA9ICJwdXJwbGUiKSArIA0KICB0aGVtZV9tYXAoKQ0KDQoNCmBgYA0KDQo0LglDaGFuZ2UgdGhlIGNvb3JkaW5hdGVzIG9mIHRoZSBtYXAgdG8gem9vbSBpbiBvbiB5b3VyIGZhdm9yaXRlIHN0YXRlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gc3RhdGVzKSArDQogICAgZ2VvbV9zZihhZXMoZmlsbCA9IGFyZWEpLCBjb2xvciA9ICJibGFjayIpKyANCiAgY29vcmRfc2YoeGxpbSA9IGMoLTEwOSwgLTEwMyksIHlsaW0gPSBjKDMwLCAzNykpICsgDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gImxpZ2h0IGJsdWUiLCBoaWdoID0gInB1cnBsZSIpICANCmBgYA0KDQoNCkhpbnQ6IGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvci8yMDE4LzEwLzI1L2dncGxvdDItc2YtMi5odG1sIGlzIGEgdXNlZnVsIHJlZmVyZW5jZSBmb3Igc29tZSBvZiB0aGUgcXVlc3Rpb25zDQoNCioqYHRpZHl0ZXh0YCB0YXNrcyoqOg0KDQpOb3cgeW91IHdpbGwgdHJ5IHVzaW5nIHRpZHl0ZXh0IG9uIGEgbmV3IGRhdGFzZXQgYWJvdXQgUnVzc2lhbiBUcm9sbCB0d2VldHMuDQoNCiMjIyMgUmVhZCBhYm91dCB0aGUgZGF0YQ0KDQpUaGVzZSBhcmUgdHdlZXRzIGZyb20gVHdpdHRlciBoYW5kbGVzIHRoYXQgYXJlIGNvbm5lY3RlZCB0byB0aGUgSW50ZXJuZXQgUmVzZWFyY2ggQWdlbmN5IChJUkEpLCBhIFJ1c3NpYW4gInRyb2xsIGZhY3RvcnkuIiAgVGhlIG1ham9yaXR5IG9mIHRoZXNlIHR3ZWV0cyB3ZXJlIHBvc3RlZCBmcm9tIDIwMTUtMjAxNywgYnV0IHRoZSBkYXRhc2V0cyBlbmNvbXBhc3MgdHdlZXRzIGZyb20gRmVicnVhcnkgMjAxMiB0byBNYXkgMjAxOC4NCg0KVGhyZWUgb2YgdGhlIG1haW4gY2F0ZWdvcmllcyBvZiB0cm9sbCB0d2VldCB0aGF0IHdlIHdpbGwgYmUgZm9jdXNpbmcgb24gYXJlIExlZnQgVHJvbGxzLCBSaWdodCBUcm9sbHMsIGFuZCBOZXdzIEZlZWQuICAqKkxlZnQgVHJvbGxzKiogdXN1YWxseSBwcmV0ZW5kIHRvIGJlIEJMTSBhY3RpdmlzdHMsIGFpbWluZyB0byBkaXZpZGUgdGhlIGRlbW9jcmF0aWMgcGFydHkgKGluIHRoaXMgY29udGV4dCwgYmVpbmcgcHJvLUJlcm5pZSBzbyB0aGF0IHZvdGVzIGFyZSB0YWtlbiBhd2F5IGZyb20gSGlsbGFyeSkuICAqKlJpZ2h0IHRyb2xscyoqIGltaXRhdGUgVHJ1bXAgc3VwcG9ydGVycywgYW5kICoqTmV3cyBGZWVkKiogaGFuZGxlcyBhcmUgImxvY2FsIG5ld3MgYWdncmVnYXRvcnMsIiB0eXBpY2FsbHkgbGlua2luZyB0byBsZWdpdGltYXRlIG5ld3MuDQoNCkZvciBvdXIgdXBjb21pbmcgYW5hbHlzZXMsIHNvbWUgaW1wb3J0YW50IHZhcmlhYmxlcyBhcmU6DQoNCiAgKiAqKmF1dGhvcioqIChoYW5kbGUgc2VuZGluZyB0aGUgdHdlZXQpDQogICogKipjb250ZW50KiogKHRleHQgb2YgdGhlIHR3ZWV0KQ0KICAqICoqbGFuZ3VhZ2UqKiAobGFuZ3VhZ2Ugb2YgdGhlIHR3ZWV0KQ0KICAqICoqcHVibGlzaF9kYXRlKiogKGRhdGUgYW5kIHRpbWUgdGhlIHR3ZWV0IHdhcyBzZW50KQ0KDQpWYXJpYWJsZSBkb2N1bWVudGF0aW9uIGNhbiBiZSBmb3VuZCBvbiBbR2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vZml2ZXRoaXJ0eWVpZ2h0L3J1c3NpYW4tdHJvbGwtdHdlZXRzLykgYW5kIGEgbW9yZSBkZXRhaWxlZCBkZXNjcmlwdGlvbiBvZiB0aGUgZGF0YXNldCBjYW4gYmUgZm91bmQgaW4gdGhpcyBbZml2ZXRoaXJ0eWVpZ2h0IGFydGljbGVdKGh0dHBzOi8vZml2ZXRoaXJ0eWVpZ2h0LmNvbS9mZWF0dXJlcy93aHktd2VyZS1zaGFyaW5nLTMtbWlsbGlvbi1ydXNzaWFuLXRyb2xsLXR3ZWV0cy8pLg0KDQpCZWNhdXNlIHRoZXJlIGFyZSAxMiBkYXRhc2V0cyBjb250YWluaW5nIDIsOTczLDM3MSB0d2VldHMgc2VudCBieSAyLDg0OCBUd2l0dGVyIGhhbmRsZXMgaW4gdG90YWwsIHdlIHdpbGwgYmUgdXNpbmcgdGhyZWUgb2YgdGhlc2UgZGF0YXNldHMgKG9uZSBmcm9tIGEgUmlnaHQgdHJvbGwsIG9uZSBmcm9tIGEgTGVmdCB0cm9sbCwgYW5kIG9uZSBmcm9tIGEgTmV3cyBGZWVkIGFjY291bnQpLg0KDQpcDQpcDQoNCjEuIFJlYWQgaW4gVHJvbGwgVHdlZXRzIERhdGFzZXQgLSB0aGlzIHRha2VzIGEgd2hpbGUuIFlvdSBjYW4gY2FjaGUgaXQgc28geW91IGRvbid0IG5lZWQgdG8gcmVhZCBpdCBpbiBhZ2FpbiBlYWNoIHRpbWUgeW91IGtuaXQuIEJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAhISEhDQoNCmBgYHtyLCBjYWNoZT1UUlVFfQ0KdHJvbGxfdHdlZXRzIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZml2ZXRoaXJ0eWVpZ2h0L3J1c3NpYW4tdHJvbGwtdHdlZXRzL21hc3Rlci9JUkFoYW5kbGVfdHdlZXRzXzEyLmNzdiIpDQpgYGANCg0KMi4gQmFzaWMgRGF0YSBDbGVhbmluZyBhbmQgRXhwbG9yYXRpb24NCg0KICBhLiBSZW1vdmUgcm93cyB3aGVyZSB0aGUgdHdlZXQgd2FzIGluIGEgbGFuZ3VhZ2Ugb3RoZXIgdGhhbiBFbmdsaXNoDQogIGIuIFJlcG9ydCB0aGUgZGltZW5zaW9ucyBvZiB0aGUgZGF0YXNldA0KICBjLiBDcmVhdGUgdHdvIG9yIHRocmVlIGJhc2ljIGV4cGxvcmF0b3J5IHBsb3RzIG9mIHRoZSBkYXRhIChleC4gcGxvdCBvZiB0aGUgZGlmZmVyZW50IGxvY2F0aW9ucyBmcm9tIHdoaWNoIHR3ZWV0cyB3ZXJlIHBvc3RlZCwgcGxvdCBvZiB0aGUgYWNjb3VudCBjYXRlZ29yeSBvZiBhIHR3ZWV0KQ0KICANCmBgYHtyfQ0KI0ENCm5ld190cm9sbHMgPC0gdHJvbGxfdHdlZXRzICU+JQ0KICBmaWx0ZXIobGFuZ3VhZ2UgPT0gIkVuZ2xpc2giKQ0KDQojQg0KDQpkaW0obmV3X3Ryb2xscykNCg0KI0MNCmdncGxvdChuZXdfdHJvbGxzKSArIA0KICBhZXMoeCA9IGFjY291bnRfY2F0ZWdvcnkpICsgDQogIGdlb21fYmFyKCkNCg0KZ2dwbG90KG5ld190cm9sbHMpICsgDQogIGFlcyh4ID0gZm9sbG93ZXJzLCB5ID0gZm9sbG93aW5nKSArIA0KICBnZW9tX3BvaW50KCkNCg0KYGBgDQoNCjMuIFVubmVzdCBUb2tlbnMNCg0KV2Ugd2FudCBlYWNoIHJvdyB0byByZXByZXNlbnQgYSB3b3JkIGZyb20gYSB0d2VldCwgcmF0aGVyIHRoYW4gYW4gZW50aXJlIHR3ZWV0LiBCZSBzdXJlIHRvIHJlbW92ZSB0aGUgYGV2YWw9RkFMU0VgISEhIQ0KDQpgYGB7cn0NCg0KICANCnRyb2xsX3R3ZWV0c191bnRva2VuIDwtIG5ld190cm9sbHMgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgY29udGVudCkNCg0KdHJvbGxfdHdlZXRzX3VudG9rZW4NCmBgYA0KDQpcDQpcDQoNCjQuIFJlbW92ZSBzdG9wd29yZHMuIEJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAhISEhDQoNCmBgYHtyfQ0KI2dldCByaWQgb2Ygc3RvcHdvcmRzICh0aGUsIGFuZCwgZXRjLikNCnRyb2xsX3R3ZWV0c19jbGVhbmVkIDwtIHRyb2xsX3R3ZWV0c191bnRva2VuICU+JQ0KICBhbnRpX2pvaW4oc3RvcF93b3JkcykNCmBgYA0KDQpUYWtlIGEgbG9vayBhdCB0aGUgdHJvbGxfdHdlZXRzX2NsZWFuZWQgZGF0YXNldC4gIEFyZSB0aGVyZSBhbnkgb3RoZXIgd29yZHMvbGV0dGVycy9udW1iZXJzIHRoYXQgd2Ugd2FudCB0byBlbGltaW5hdGUgdGhhdCB3ZXJlbid0IHRha2VuIGNhcmUgb2YgYnkgc3RvcF93b3Jkcz8gQmUgc3VyZSB0byByZW1vdmUgdGhlIGBldmFsPUZBTFNFYCEhISENCg0KYGBge3J9DQojZ2V0IHJpZCBvZiBodHRwLCBodHRwcywgdC5jbywgcnQsIGFtcCwgc2luZ2xlIG51bWJlciBkaWdpdHMsIGFuZCBzaW5ndWxhciBsZXR0ZXJzDQp0cm9sbF90d2VldHNfY2xlYW5lZCA8LSB0cm9sbF90d2VldHNfY2xlYW5lZCAlPiUNCiAgZmlsdGVyKHdvcmQgIT0gImh0dHAiLCANCiAgICAgICAgIHdvcmQgIT0gImh0dHBzIiwNCiAgICAgICAgIHdvcmQgIT0gICJ0LmNvIiwNCiAgICAgICAgIHdvcmQgIT0gInJ0IiwNCiAgICAgICAgIHdvcmQgIT0gImFtcCIsIA0KICAgICAgICAgd29yZCAhPSAiMSIsIA0KICAgICAgICAgd29yZCAhPSAiMiIsIA0KICAgICAgICAgd29yZCAhPSAiMyIsIA0KICAgICAgICAgd29yZCAhPSAiNCIsIA0KICAgICAgICAgd29yZCAhPSAiNSIsIA0KICAgICAgICAgd29yZCAhPSAiNiIsIA0KICAgICAgICAgd29yZCAhPSAiNyIsIA0KICAgICAgICAgd29yZCAhPSAiOCIsIA0KICAgICAgICAgd29yZCAhPSAiOSIpICMgeW91IGNhbiB1c2UgJWluJSBmb3IgYSBsaXN0IG9mIHdvcmRzDQpgYGANCg0KDQo1LiBMb29rIGF0IGEgc3Vic2V0IG9mIHRoZSB0d2VldHMgdG8gc2VlIGhvdyBvZnRlbiB0aGUgdG9wIHdvcmRzIGFwcGVhci4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD04fQ0KdHJvbGxfdHdlZXRzX3NtYWxsIDwtIHRyb2xsX3R3ZWV0c19jbGVhbmVkICU+JQ0KICBjb3VudCh3b3JkKSAlPiUNCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gbiwgbiA9IDUwKSAjIDUwIG1vc3Qgb2NjdXJyaW5nIHdvcmRzDQoNCiMgdmlzdWFsaXplIHRoZSBudW1iZXIgb2YgdGltZXMgdGhlIDUwIHRvcCB3b3JkcyBhcHBlYXINCmdncGxvdCh0cm9sbF90d2VldHNfc21hbGwsIA0KICAgICAgIGFlcyh5ID0gZmN0X3Jlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArDQogIGdlb21fY29sKCkNCmBgYA0KDQoNCjYuIFNlbnRpbWVudCBBbmFseXNpcw0KDQogIGEuIEdldCB0aGUgc2VudGltZW50cyB1c2luZyB0aGUgImJpbmciIHBhcmFtZXRlciAod2hpY2ggY2xhc3NpZmllcyB3b3JkcyBpbnRvICJwb3NpdGl2ZSIgb3IgIm5lZ2F0aXZlIikNCiAgYi4gUmVwb3J0IGhvdyBtYW55IHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3JkcyB0aGVyZSBhcmUgaW4gdGhlIGRhdGFzZXQuICBBcmUgdGhlcmUgbW9yZSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSB3b3JkcywgYW5kIHdoeSBkbyB5b3UgdGhpbmsgdGhpcyBtaWdodCBiZT8NCiAgDQpCZSBzdXJlIHRvIHJlbW92ZSB0aGUgYGV2YWw9RkFMU0VgISEhIQ0KDQpgYGB7cn0NCiMgbG9vayBhdCBzZW50aW1lbnQNCnNlbnRpbWVudHMgPC1nZXRfc2VudGltZW50cygiYmluZyIpDQoNCiMgYXNzaWduIGEgc2VudGltZW50IHRvIGVhY2ggd29yZCB0aGF0IGhhcyBvbmUgYXNzb2NpYXRlZA0KdHJvbGxfdHdlZXRzX3NlbnRpbWVudCA8LSB0cm9sbF90d2VldHNfY2xlYW5lZCAlPiUNCiAgaW5uZXJfam9pbihzZW50aW1lbnRzKQ0KDQojIGNvdW50IHRoZSBzZW50aW1lbnRzDQp0cm9sbF90d2VldHNfc2VudGltZW50ICU+JQ0KICBjb3VudChzZW50aW1lbnQpDQpgYGANCg0KVGhlcmUgYXJlIG1vcmUgbmVnYXRpdmUgd29yZHMgdGhhbiBwb3NpdGl2ZSBiZWNhdXNlIHRoZSBmYWtlIHR3ZWV0cyBhcmUgdHJ5aW5nIHRvIHRlYXIgZG93biB0aGUgY29tcGV0aXRpb24gd2l0aCB0aGVpciB0d2VldHMuIA0KDQo3LiBVc2luZyB0aGUgdHJvbGxfdHdlZXRzX3NtYWxsIGRhdGFzZXQsIG1ha2UgYSB3b3JkY2xvdWQ6DQoNCiAgYS4gVGhhdCBpcyBzaXplZCBieSB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoYXQgYSB3b3JkIGFwcGVhcnMgaW4gdGhlIHR3ZWV0cw0KICBiLiBUaGF0IGlzIGNvbG9yZWQgYnkgc2VudGltZW50IChwb3NpdGl2ZSBvciBuZWdhdGl2ZSkNCg0KDQpCZSBzdXJlIHRvIHJlbW92ZSB0aGUgYGV2YWw9RkFMU0VgISEhIQ0KDQpgYGB7cn0NCiMgbWFrZSBhIHdvcmRjbG91ZCB3aGVyZSB0aGUgc2l6ZSBvZiB0aGUgd29yZCBpcyBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoZSB3b3JkIGFwcGVhcnMgYWNyb3NzIHRoZSB0d2VldHMNCg0KdHJvbGxfdHdlZXRzX3NtYWxsICU+JQ0KICB3aXRoKHdvcmRjbG91ZCh3b3JkLCBuLCBtYXgud29yZHMgPSAxMikpDQoNCiMgbWFrZSBhIHdvcmRjbG91ZCBjb2xvcmVkIGJ5IHNlbnRpbWVudA0KDQp0cm9sbF90d2VldHNfc2VudGltZW50ICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lDQogIGNvbXBhcmlzb24uY2xvdWQoY29sb3JzID0gYygiYmx1ZSIsICJvcmFuZ2UiKSwNCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAyMCkNCmBgYA0KDQpBcmUgdGhlcmUgYW55IHdvcmRzIHdob3NlIGNhdGVnb3JpemF0aW9uIGFzICJwb3NpdGl2ZSIgb3IgIm5lZ2F0aXZlIiBzdXJwcmlzZWQgeW91Pw0KDQp0cnVtcCBhcyBwb3NpdGl2ZSBzdXJwcmlzZWQgbWUgZm9yIHN1cmUgYXMgYnJlYWtpbmcgYmVpbmcgbmVnYXRpdmUganVzdCBiZWNhdXNlIG9mIHRoZSBtYW55IGRpZmZlcmVudCB1c2VzIG9mIHRoZSB3b3JkIGJyZWFraW5nLCBsaWtlIGJyZWFraW5nIG5ld3MsIGlzbid0IG5lY2Vzc2FyaWx5IG5lZ2F0aXZlLiANCg0KIyMgUHJvamVjdHMNCg0KUmVhZCB0aGUgcHJvamVjdCBkZXNjcmlwdGlvbiBvbiB0aGUgbW9vZGxlIHBhZ2UuIFRhbGsgdG8geW91ciBncm91cCBtZW1iZXJzIGFib3V0IHBvdGVudGlhbCB0b3BpY3MuIA0KDQoNCg0KKipUYXNrOioqDQoNCldyaXRlIGEgc2hvcnQgcGFyYWdyYXBoIGFib3V0IGlkZWFzIHlvdSBoYXZlLiBJZiB5b3UgYWxyZWFkeSBoYXZlIHNvbWUgZGF0YSBzb3VyY2VzIGluIG1pbmQsIHlvdSBjYW4gbGluayB0byB0aG9zZSwgYnV0IEknbSBtb3JlIGNvbmNlcm5lZCB3aXRoIHlvdSBoYXZpbmcgYSB0b3BpYyB0aGF0IHlvdSdyZSBpbnRlcmVzdGVkIGluIGludmVzdGlnYXRpbmcgcmlnaHQgbm93LiANCg0KV2UgYXJlIGNvbnNpZGVyaW5nIGRvaW5nIHByb2plY3RzIHJlbGF0ZWQgdG8gc3BvcnRzIG9yIGNyaW1lLCB3ZSBoYXZlIHRob3VnaHQgb2YgdXNpbmcgZmlmYSB2aWRlbyBnYW1lIGRhdGEgdG8gcHJlZGljdCBiaWFzZXMgb2YgdGhlIGdhbWUsIHdlIGhhdmUgYWxzbyBjb21lIHVwIHdpdGggdGhlIGlkZWEgdG8gY2x1c3RlciBkaWZmZXJlbnQgYmFzZWJhbGwgcGxheWVycyBpbnRvIGRpZmZlcmVudCBwbGF5ZXIgdHlwZXMsIGFuZCB0aGVuIGFuYWx5emluZyB3aGF0IHRlYW0gbWFrZXVwIG1ha2VzIHRoZSBiZXN0IHRlYW0uIFdlIGFsc28gaGF2ZSBkaXNjdXNzZWQgY3JlYXRpbmcgYSByZWNvbW1lbmRlciBwcm9qZWN0LCBzaW1pbGFyIHRvIG5ldGZsaXggb3IgZ29vZ2xlIHNlYXJjaGVzLiANCg0KDQojIyAiVW5kb2luZyIgYmlhcw0KDQoqKlRhc2s6KioNCg0KUmVhZCB0aGlzIHR3ZWV0IFt0aHJlYWRdKGh0dHBzOi8vdGhyZWFkcmVhZGVyYXBwLmNvbS90aHJlYWQvMTM3NTk1NzI4NDA2MTM3NjUxNi5odG1sKSBieSBbRGViIFJhamldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RlYm9yYWhfUmFqaSkgd2hvIHlvdSBtYXkgcmVtZW1iZXIgZnJvbSB0aGUgKkNvZGVkIEJpYXMqIGZpbG0uIFdyaXRlIGEgc2hvcnQgcGFyYWdyYXBoIHRoYXQgZGlzY3Vzc2VzIGF0IGxlYXN0IG9uZSBvZiB0aGUgbWlzY29uY2VwdGlvbnMuDQoNCkl0IHdhcyBpbnRlcmVzdGluZyB0byBtZSB3aGVuIHNoZSBicm91Z2h0IHVwIHRoZSBwb2ludCB0aGF0IHRoZSBiaWFzIGNhbiBiZWdpbiBhbnl3aGVyZSBpbiB0aGUgcHJvY2Vzcy4gQWxsIHRoZSB3YXkgYmFjayB0byB0aGUgZGF0YSBjb2xsZWN0aW9uIHByb2Nlc3MgdGhpbmdzIGNhbiBiZSBiaWFzZWQuIFdoZW4geW91IGFyZSBkb2luZyB5b3VyIHByZXByb2Nlc3NpbmcsIHRoZSBwcm9jZXNzIGNvdWxkIGJlIGJpYXNlZC4gSSB0aGluayB0aGF0IHRoaXMgbmVlZHMgdG8gYmUgcmVjb2duaXplZCBldmVuIG1vcmUgYW5kIHRob3VnaHQgYWJvdXQgd2hpbGUgd29ya2luZyB3aXRoIGRhdGEuIA==